package pho.prowon.category;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import pho.helper.SuffixFileNameFilter;
import pho.helper.XMLHelper;
import cpa.prowon.Application;

//@TODO Should -i COmmand Line Option override the default index name???

/**
 * @author Peter
 *
 * This class is the starting point to retrieve the available IndexCategoryDefinitions. It reads the xml-files
 * in the directory referenced by the CATEGORY_DEFINITIONS_PROPERTY and tries to extract the category definitions
 * from there. In addition the default-category 'By Path' is always added.
 */
public class IndexCategoryDefinitionFactory
{
	public static final String PROPNAME_CATEGORYDEFINITION_FILEPATH = "prowon.CategoryDefinitions";
	
	private static IndexCategoryDefinitionFactory _instance;
	private ArrayList definitions = new ArrayList();

	/**
	 * To retrieve the sole instance of the factory call this method.
	 * @return the singleton factory instance
	 */ 
	public static IndexCategoryDefinitionFactory getInstance()
	{
		if (_instance == null)
		{
			_instance = new IndexCategoryDefinitionFactory();
		}
		return _instance;
	}

	/**
	 * construct the factory singleton and initialize the category definitions which are found to 
	 * be available. This can only be invoked through the static getInstance method.
	 * The default category 'By PAth' is hardcoded and will always be added automatically.
	 */ 
	private IndexCategoryDefinitionFactory()
	{
		// get the name of the file containing the index categorydefintions
		String folderName =	System.getProperty(PROPNAME_CATEGORYDEFINITION_FILEPATH, "catdefs");		
		File folder = new File(folderName);
		
		if (folder.exists() && folder.isDirectory())
		{
			List fileList =	Arrays.asList(folder.listFiles(new SuffixFileNameFilter(".xml")));
			Iterator files = fileList.iterator();
		
			FileInputStream is;
			while (files.hasNext())
			{
				File defFile = (File)files.next();
				try
				{
					is = new FileInputStream(defFile);
					System.out.println(
						"Found category definitions ["
							+ defFile.getName()
							+ "]");
					initialize(is);
				}
				catch (FileNotFoundException e)
				{
					// take default category By path --> this shouldn't happen as we are asking the folder for
					// its files but anyway
					System.out.println(
						"Had a problem with category definitions ["
							+ defFile.getName()
							+ "]");
				}
			}
		}
		else
		{
			System.out.println(
				"Folder with category definitions ["
					+ folderName
					+ "] does not exist");
		}

		String catName = "";
		String catProp = "category_bypath";
		try
		{
			catName = Application.getInstance().getResourceBundle().getString(catProp);
		}
		catch (MissingResourceException e)
		{
			catName = "By Path";
			catProp = "";
			System.out.println(e);
		}

		// add the default category 'By Path'		
		definitions.add(
			new IndexCategoryDefinition(
				"$$PATH$$",catName, catProp));
	}

  /**
   * initializes IndexCategoryDefinitions from the InputStream passed which is supposed to deliver
   * a xml file in the correct format. If this is not the case the stream is ignored and no
   * IndexCategoryDefinitions are created. 
   * @param is the input stream containing the XML-definitions 
   */ 

	private void initialize(InputStream is)
	{
		Document document = XMLHelper.loadDocumentFromStream(is);
		Element element;

		if (document != null)
		{
			String defaultCatDefName = getDefaultCatDefName();

			element = document.getDocumentElement();
			NodeList nl = element.getElementsByTagName(getDefinitionElement());
			for (int i = 0; i < nl.getLength(); i++)
			{
				Node propNode = null;
				Node textNode = null;
				String catNameProperty = "";
				String catName = "";

				element = (Element) nl.item(i);
				if (element != null)
				{
					// first we try to find a property for the name in the XML
					propNode = element.getElementsByTagName("Property").item(0);
				}
				
				if (propNode != null)
				{
					textNode = propNode.getFirstChild();
				}

				if (textNode != null)
				{
					try
					{
						catNameProperty = textNode.getNodeValue();
						catName = Application.getInstance()
								.getResourceBundle()
								.getString(catNameProperty);
					}
					catch (ClassCastException e)
					{
						System.out.println(e);						
						catNameProperty = "";
					}
					catch (NullPointerException e)
					{
						System.out.println(e);
						catNameProperty = "";
					}
					catch (MissingResourceException e)
					{
						System.out.println(e);
						catNameProperty = "";
					}
					catch (DOMException e)
					{
						System.out.println(e);						
						catNameProperty = "";
					}
				}

				if (catName.equals(""))
				{
					catName = getLiteralCategoryName(element);

					if (catName.equals(""))
					{
						// hardcoded default
						catName = defaultCatDefName;
					}
				}

				List al = getCategoryTags(element);
				
				// only add it to the available definitions if it contained at least one
				// category which is not the empty string
				if (al.size() > 0)
				{
					definitions.add(new IndexCategoryDefinition(al, catName, catNameProperty));
				}
			}
		}
	}

	private String getLiteralCategoryName(Element element)
	{
		Node propNode;
		Node textNode;
		String catName;
		catName = "";
		// if no property exists or it cannot be resolved properly,
		// try to find a literal name in the XML
		propNode = (element.getElementsByTagName("Name").item(0));
		
		if (propNode != null)
		{
			textNode = propNode.getFirstChild();
			if (textNode != null)
			{
				try
				{
					catName = textNode.getNodeValue();
				}
				catch (DOMException e)
				{
					System.out.println(e);						
				}							
			}
		}
		return catName;
	}

	private List getCategoryTags(Element element)
	{
		// now go for the category tags
		ArrayList al = new ArrayList();
		if (element != null)
		{
			NodeList nlTags = element.getElementsByTagName("Tag");
			for (int j = 0; j < nlTags.getLength(); j++)
			{
				Element catElement = (Element) nlTags.item(j);
				if (catElement != null)
				{
					Node valueChild = (Node) catElement.getFirstChild();
					if (valueChild != null)
					{
						String catTag = valueChild.getNodeValue();
						if (catTag != null && !catTag.equals(""))
						{
							al.add(catTag);
						}
					}
				}
			}
		}
		return al;
	}

	private String getDefaultCatDefName()
	{
		String defaultCatDefName = "";
		try
		{
			defaultCatDefName =
				Application.getInstance().getResourceBundle().getString(
					"category_unnamed");
		}
		catch (MissingResourceException e)
		{
			System.out.println (e);
			defaultCatDefName = "<Unnamed category>";
		}
		return defaultCatDefName;
	}

	private String getDefinitionElement()
	{
		return "Definition";
	}

	/**
	 * return the list with all available IndexCategoryDefinitions
	 * @param a list containing IndexCategoryDefinitions 
	 */ 
	public List availableDefinitions()
	{
		return (List) definitions.clone();
	}

	/**
	 * retrieves the (first) IndexCategoryDefinition with the literal
	 * name (language dependent value) 
	 * @param name the (language patched) name to be looked for
	 * @return the found IndexCategoryDefintion, null if none matches the name
	 */ 
	public IndexCategoryDefinition definitionFor(String name)
	{
		Iterator it = definitions.iterator();
		while (it.hasNext())
		{
			IndexCategoryDefinition icd = (IndexCategoryDefinition) it.next();
			if (icd.getName().equals(name))
			{
				return icd;
			}
		}
		return null;
	}

	/**
	 * retrieves the (first) IndexCategoryDefinition whose name is derived
	 * from the language bundle string that was passed as parameter
	 * @param name the language bundle string to be looked for
	 * @return the found IndexCategoryDefintion, null if none matches the name
	 */ 
	public IndexCategoryDefinition definitionForProperty(String nameProperty)
	{
		Iterator it = definitions.iterator();
		while (it.hasNext())
		{
			IndexCategoryDefinition icd = (IndexCategoryDefinition) it.next();
			if (icd.getNameProperty().equals(nameProperty))
			{
				return icd;
			}
		}
		return null;
	}
}
